// MultiJointModel.js (c) 2012 matsuda and itami
// Vertex shader program
var VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute vec4 a_Normal;
uniform mat4 u_MvpMatrix;
uniform mat4 u_NormalMatrix;
varying vec4 v_Color;
void main() {
  gl_Position = u_MvpMatrix * a_Position;
  // Shading calculation to make the arm look three-dimensional
  vec3 lightDirection = normalize(vec3(0.0, 0.5, 0.7)); // Light direction
  vec4 color = vec4(1.0, 0.4, 0.0, 1.0); // Robot color
  vec3 normal = normalize((u_NormalMatrix * a_Normal).xyz);
  float nDotL = max(dot(normal, lightDirection), 0.0);
  v_Color = vec4(color.rgb * nDotL + vec3(0.1), color.a);
}`;

// Fragment shader program
var FSHADER_SOURCE = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_Color;
void main() {
  gl_FragColor = v_Color;
}
`;

function main() {
  // Retrieve <canvas> element
  var canvas = document.getElementById("webgl");

  // Get the rendering context for WebGL
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log("Failed to get the rendering context for WebGL");
    return;
  }

  // Initialize shaders
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log("Failed to intialize shaders.");
    return;
  }

  // Set the vertex information
  var n = initVertexBuffers(gl);
  if (n < 0) {
    console.log("Failed to set the vertex information");
    return;
  }

  // Set the clear color and enable the depth test
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.enable(gl.DEPTH_TEST);

  // Get the storage locations of uniform variables
  var u_MvpMatrix = gl.getUniformLocation(gl.program, "u_MvpMatrix");
  var u_NormalMatrix = gl.getUniformLocation(gl.program, "u_NormalMatrix");
  if (!u_MvpMatrix || !u_NormalMatrix) {
    console.log("Failed to get the storage location");
    return;
  }

  // Calculate the view projection matrix
  var viewProjMatrix = new Matrix4();
  viewProjMatrix.setPerspective(50.0, canvas.width / canvas.height, 1.0, 100.0);
  viewProjMatrix.lookAt(40.0, 20.0, 60.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

  // Register the event handler to be called on key press
  document.onkeydown = function (ev) {
    keydown(ev, gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
  };

  draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // Draw the robot arm
}

var MAX_ANGLE_STEP = 20;

var ANGLE_STEP = 1.0; 
var g_angle = 0.0; 

function keydown(ev, gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {
  switch (ev.keyCode) {
    case 39:
      g_angle = g_angle + ANGLE_STEP;
      if (g_angle >= MAX_ANGLE_STEP) {
        ANGLE_STEP = -ANGLE_STEP;
      }
      if (g_angle <= -MAX_ANGLE_STEP) {
        ANGLE_STEP = -ANGLE_STEP;
      }
      break;
    case 37:
      g_angle = g_angle + ANGLE_STEP;
      if (g_angle >= MAX_ANGLE_STEP) {
        ANGLE_STEP = -ANGLE_STEP;
      }
      if (g_angle <= -MAX_ANGLE_STEP) {
        ANGLE_STEP = -ANGLE_STEP;
      }
      break;
    default:
      return;
  }
  draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
}

function initVertexBuffers(gl) {
  // Write the vertex property to buffers (coordinates and normals)
  if (!initArrayBuffer(gl, "a_Position", vertices, gl.FLOAT, 3)) return -1;
  if (!initArrayBuffer(gl, "a_Normal", normals, gl.FLOAT, 3)) return -1;

  // Unbind the buffer object
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  // Write the indices to the buffer object
  var indexBuffer = gl.createBuffer();
  if (!indexBuffer) {
    console.log("Failed to create the buffer object");
    return -1;
  }
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

  return indices.length;
}

function initArrayBuffer(gl, attribute, data, type, num) {
  // Create a buffer object
  var buffer = gl.createBuffer();
  if (!buffer) {
    console.log("Failed to create the buffer object");
    return false;
  }
  // Write date into the buffer object
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

  // Assign the buffer object to the attribute variable
  var a_attribute = gl.getAttribLocation(gl.program, attribute);
  if (a_attribute < 0) {
    console.log("Failed to get the storage location of " + attribute);
    return false;
  }
  gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
  // Enable the assignment of the buffer object to the attribute variable
  gl.enableVertexAttribArray(a_attribute);

  return true;
}

// Coordinate transformation matrix
var g_modelMatrix = new Matrix4(),
  g_mvpMatrix = new Matrix4();

const body_length = 12;
// Arm1
var arm_Length = 12.0;

function draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {
  // Clear color and depth buffer
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  // Draw body
  g_modelMatrix.setTranslate(0.0, -6.0, 0.0);
  drawBox(
    gl,
    n,
    5.0,
    body_length,
    5.0,
    viewProjMatrix,
    u_MvpMatrix,
    u_NormalMatrix
  );

  // Draw head
  pushMatrix(g_modelMatrix);
  g_modelMatrix.setTranslate(0.0, -6 + body_length, 0.0);
  drawBox(gl, n, 8.0, 8, 8.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
  g_modelMatrix = popMatrix();
  pushMatrix(g_modelMatrix);

  // Draw left leg
  g_modelMatrix.rotate(150, 0.0, 0.0, -1.0);
  g_modelMatrix.rotate(g_angle, 1.0, 0.0, 0.0);
  drawBox(gl, n, 3.0, 20, 3.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
  g_modelMatrix = popMatrix();
  // Draw right leg
  pushMatrix(g_modelMatrix);
  g_modelMatrix.rotate(-150, 0.0, 0.0, -1.0);
  g_modelMatrix.rotate(-g_angle, 1.0, 0.0, 0.0);
  drawBox(gl, n, 3.0, 20, 3.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);

  g_modelMatrix = popMatrix();
  g_modelMatrix.translate(0.0, 8, 0.0);
  pushMatrix(g_modelMatrix);
  g_modelMatrix.rotate(130, 0.0, 0.0, 1.0);
  g_modelMatrix.rotate(g_angle, 1.0, 0.0, 0.0);
  drawBox(gl, n, 3.0, 12, 3.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
  g_modelMatrix = popMatrix();
  pushMatrix(g_modelMatrix);
  g_modelMatrix.rotate(-130, 0.0, 0.0, 1.0);
  g_modelMatrix.rotate(-g_angle, 1.0, 0.0, 0.0);
  drawBox(gl, n, 3.0, 12, 3.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // Draw
  pushMatrix(g_modelMatrix);
}

var g_matrixStack = []; // Array for storing a matrix
function pushMatrix(m) {
  // Store the specified matrix to the array
  var m2 = new Matrix4(m);
  g_matrixStack.push(m2);
}

function popMatrix() {
  // Retrieve the matrix from the array
  return g_matrixStack.pop();
}

var g_normalMatrix = new Matrix4(); // Coordinate transformation matrix for normals

// Draw rectangular solid
function drawBox(
  gl,
  n,
  width,
  height,
  depth,
  viewProjMatrix,
  u_MvpMatrix,
  u_NormalMatrix
) {
  pushMatrix(g_modelMatrix); // Save the model matrix
  // Scale a cube and draw
  g_modelMatrix.scale(width, height, depth);
  // Calculate the model view project matrix and pass it to u_MvpMatrix
  g_mvpMatrix.set(viewProjMatrix);
  g_mvpMatrix.multiply(g_modelMatrix);
  gl.uniformMatrix4fv(u_MvpMatrix, false, g_mvpMatrix.elements);
  // Calculate the normal transformation matrix and pass it to u_NormalMatrix
  g_normalMatrix.setInverseOf(g_modelMatrix);
  g_normalMatrix.transpose();
  gl.uniformMatrix4fv(u_NormalMatrix, false, g_normalMatrix.elements);
  // Draw
  gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
  g_modelMatrix = popMatrix(); // Retrieve the model matrix
}
